from flask import Flask,render_template, redirect, url_for, request
from flask_login import LoginManager, UserMixin, login_user, current_user, login_required, logout_user # 引入用户基类
from flask_wtf import FlaskForm
from werkzeug.security import generate_password_hash, check_password_hash
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired
import uuid
# from flaskext.auth import Auth
# import flask_security
app = Flask(__name__)  # 创建 Flask 应用
app.secret_key = 'abc'  # 设置表单交互密钥
login_manager = LoginManager()  # 实例化登录管理对象
login_manager.init_app(app)  # 初始化应用
login_manager.login_view = 'login'  # 设置用户登录视图函数 endpoint



USERS = [
    {
        "id": 1,
        "name": 'lily',
        "password": generate_password_hash('123')
    },
    {
        "id": 2,
        "name": 'tom',
        "password": generate_password_hash('123')
    }
]
def create_user(user_name, password):
    """创建一个用户"""
    user = {
        "name": user_name,
        "password": generate_password_hash(password),
        "id": uuid.uuid4()
    }
    USERS.append(user)

def get_user(user_name):
    """根据用户名获得用户记录"""
    for user in USERS:
        if user.get("name") == user_name:
            return user
    return None

class User(UserMixin):
    """用户类"""
    def __init__(self, user):
        self.username = user.get("name")
        self.password_hash = user.get("password")
        self.id = user.get("id")

    def verify_password(self, password):
        """密码验证"""
        if self.password_hash is None:
            return False
        return check_password_hash(self.password_hash, password)

    def get_id(self):
        """获取用户ID"""
        return self.id

    @staticmethod
    def get(user_id):
        """根据用户ID获取用户实体，为 login_user 方法提供支持"""
        if not user_id:
            return None
        for user in USERS:
            if user.get('id') == user_id:
                return User(user)
        return None

    @login_manager.user_loader  # 定义获取登录用户的方法
    def load_user(user_id):
        return User.get(user_id)


# ...




class LoginForm(FlaskForm):
    """登录表单类"""
    username = StringField('用户名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired()])


# ...
@app.route('/login/', methods=('GET', 'POST'))  # 登录
def login():
    form = LoginForm()
    emsg = None
    if form.validate_on_submit():
        user_name = form.username.data
        password = form.password.data
        user_info = get_user(user_name)  # 从用户数据中查找用户记录
        if user_info is None:
            emsg = "用户名或密码密码有误"
        else:
            user = User(user_info)  # 创建用户实体
            if user.verify_password(password):  # 校验密码
                login_user(user)  # 创建用户 Session
                return redirect(request.args.get('next') or url_for('index'))
            else:
                emsg = "用户名或密码密码有误"
    return render_template('login.html', form=form, emsg=emsg)

# ...
@app.route('/')  # 首页
@login_required  # 需要登录才能访问
def index():
    return render_template('index.html', username=current_user.username)


# ...
@app.route('/logout')  # 登出
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

'''
Session 防护
Session 信息一般存放在 cookie 中，但是 cookie 不够安全，容易被窃取其中 Session 信息，伪造用户登录系统，
幸运的是 Flask-Login 提供了 Session 防护机制，提供有 basic 和 strong 两种保护等级，通过 login_manager.session_protection 来开关和设置等级，
默认等级为 basic，如果设置为 None 将关闭 Session 防护机制。

在保护机制开启的情况下，每次请求会根据用户的特征（一般指有用户IP、浏览器类型生成的哈希码）与 Session 中的对比，
如果无法匹配则要求用户重新登录，在强模式下( strong )一旦匹配失败会删除登录者 Session，以消除攻击者重构 cookie 的可能

Request Loader
有时候因为一些原因不想或者无法使用 cookie，可以将 Session 记录在其他地方，比如 Header 中或者请求参数中，
那么构造用户 Session 时就需要将 user_loader 替换为 request_loader， request_loader 将 request 作为参数，这样就可以从请求的任何数据中获取 Session 信息了
'''
if __name__ == '__main__':
    app.run(debug=True)